# Maps.java

    package org.codefilarete.tool.collection;

    import java.util.Comparator;
    import java.util.HashMap;
    import java.util.LinkedHashMap;
    import java.util.Map;
    import java.util.TreeMap;
    import java.util.function.Supplier;

    /**
     * @author Guillaume Mary
     */
    public final class Maps {

    	/**
    	 * Allows chaining of the {@link ChainingMap#add(Object, Object)} method and then easily create a Map
    	 * @return a new Map instance
    	 */
    	public static <K, V> ChainingMap<K, V> asMap(K key, V value) {
    		return new ChainingMap<K, V>().add(key, value);
    	}

    	/**
    	 * Allows chaining of the {@link ChainingHashMap#add(Object, Object)} method and then easily create a Map
    	 * @return a new HashMap instance
    	 */
    	public static <K, V> ChainingHashMap<K, V> asHashMap(K key, V value) {
    		return new ChainingHashMap<K, V>().add(key, value);
    	}

    	/**
    	 * A simple factory method that takes only expected key and value types, made to avoid casting when using method factory with key and value
    	 * (that next call to add(..) requires casting of key and/or values)
    	 *
    	 * @param keyType key type
    	 * @param valueType value type
    	 * @param <K> key type
    	 * @param <V> value type
    	 * @return a new {@link HashMap} that allows chaining of additional key and value
    	 */
    	public static <K, V> ChainingHashMap<K, V> forHashMap(Class<K> keyType, Class<V> valueType) {
    		return new ChainingHashMap<>();
    	}

    	/**
    	 * Returns a {@link Map} of given {@link Map}s values that are mapped on the same keys in both {@link Map}s.
    	 *
    	 * @param map1 a {@link Map}
    	 * @param map2 a {@link Map}
    	 * @param <K> {@link Map}s key type
    	 * @param <V1> first {@link Map} values type
    	 * @param <V2> second {@link Map} values type
    	 * @return a {@link Map} of values that are in both {@link Map}s and mapped under the same keys
    	 */
    	public static <K, V1, V2> Map<V1, V2> innerJoin(Map<K, V1> map1, Map<K, V2> map2) {
    		Map<V1, V2> result = new HashMap<>();
    		map1.forEach((k, v) -> {
    			if (map2.containsKey(k)) {
    				result.put(v, map2.get(k));
    			}
    		});
    		return result;
    	}

    	/**
    	 * Returns a {@link Map} of given first {@link Map} keys and second {@link Map} values
    	 * by joining them on first {@link Map} values and second {@link Map} keys.
    	 *
    	 * @param map1 a {@link Map}
    	 * @param map2 a {@link Map}
    	 * @param <K> {@link Map}s key type
    	 * @param <V1> first {@link Map} values and second {@link Map} keys type
    	 * @param <V2> second {@link Map} values type
    	 * @return a {@link Map} of first {@link Map} keys and second {@link Map} values joined on first {@link Map} values and second {@link Map} keys.
    	 */
    	public static <K, V1, V2> Map<K, V2> innerJoinOnValuesAndKeys(Map<K, V1> map1, Map<V1, V2> map2) {
    		return innerJoinOnValuesAndKeys(map1, map2, HashMap::new);
    	}

    	/**
    	 * Returns a {@link Map} of given first {@link Map} keys and second {@link Map} values
    	 * by joining them on first {@link Map} values and second {@link Map} keys.
    	 *
    	 * @param map1 a {@link Map}
    	 * @param map2 a {@link Map}
    	 * @param supplier supplier for resulting {@link Map}, allows changing its type, size it, etc.
    	 * @param <K> {@link Map}s key type
    	 * @param <V1> first {@link Map} values and second {@link Map} keys type
    	 * @param <V2> second {@link Map} values type
    	 * @return a {@link Map} of first {@link Map} keys and second {@link Map} values joined on first {@link Map} values and second {@link Map} keys.
    	 */
    	public static <K, V1, V2, M extends Map<K, V2>> M innerJoinOnValuesAndKeys(Map<K, V1> map1, Map<V1, V2> map2, Supplier<M> supplier) {
    		M result = supplier.get();
    		map1.forEach((k, v1) -> {
    			V2 v2 = map2.get(v1);
    			result.put(k, v2);
    		});
    		return result;
    	}

    	/**
    	 * Puts all elements of map1 and map2 into a new {@link HashMap}.
    	 * Made to have a putAll(..) method inlined.
    	 *
    	 * @param map1 any non null {@link Map}
    	 * @param map2 any non null {@link Map}
    	 * @param <K> {@link Map}s key type
    	 * @param <V> {@link Map}s value type
    	 * @return a new {@link HashMap} containing elements of map1 and
    	 */
    	public static <K, V> Map<K, V> putAll(Map<? extends K, ? extends V> map1, Map<? extends K, ? extends V> map2) {
    		Map<K, V> result = new HashMap<>(map1);
    		result.putAll(map2);
    		return result;
    	}

    	/**
    	 * Simple {@link LinkedHashMap} that allows to chain calls to {@link #add(Object, Object)} (same as put) and so quickly create a Map.
    	 *
    	 * @param <K>
    	 * @param <V>
    	 */
    	public static class ChainingMap<K, V> extends LinkedHashMap<K, V> {

    		public ChainingMap() {
    			super();
    		}

    		public ChainingMap<K, V> add(K key, V value) {
    			put(key, value);
    			return this;
    		}
    	}

    	/**
    	 * Simple {@link HashMap} that allows to chain calls to {@link #add(Object, Object)} (same as put) and so quickly create a Map.
    	 *
    	 * @param <K>
    	 * @param <V>
    	 */
    	public static class ChainingHashMap<K, V> extends HashMap<K, V> {

    		public ChainingHashMap() {
    			super();
    		}

    		public ChainingHashMap<K, V> add(K key, V value) {
    			put(key, value);
    			return this;
    		}
    	}

    	/**
    	 * Simple {@link TreeMap} that allows to chain calls to {@link #add(Object, Object)} (same as put) and so quickly create a Map.
    	 *
    	 * @param <K>
    	 * @param <V>
    	 */
    	public static class ChainingComparingMap<K, V> extends TreeMap<K, V> {

    		public ChainingComparingMap(Comparator<K> comparator) {
    			super(comparator);
    		}

    		public ChainingComparingMap<K, V> add(K key, V value) {
    			put(key, value);
    			return this;
    		}
    	}
    }